package com.umeox.watch.httpclient.core.client;

import android.text.TextUtils;
import android.util.Log;

import com.umeox.watch.httpclient.core.ApiCallback;
import com.umeox.watch.httpclient.core.ApiRequest;
import com.umeox.watch.httpclient.core.ApiResponse;
import com.umeox.watch.httpclient.core.constant.Scheme;
import com.umeox.watch.httpclient.core.exception.SdkException;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class HttpApiClient extends BaseApiClient {
    private OkHttpClient client;

    protected HttpApiClient() {
    }

    public void init(HttpClientBuilderParams buildParam) {
        if (null == buildParam) {
            throw new SdkException("buildParam must not be null");
        } else {
            buildParam.check();
            this.host = buildParam.getHost();
            this.scheme = buildParam.getScheme();
            this.interceptors = buildParam.getInterceptors();
            OkHttpClient.Builder builder;
            if (this.scheme == Scheme.HTTPS) {
                builder = new OkHttpClient.Builder()
                        .sslSocketFactory(buildParam.getSslSocketFactory(), buildParam.getX509TrustManager())
                        .hostnameVerifier(buildParam.getHostnameVerifier());
            } else {
                builder = new OkHttpClient.Builder();
            }
            if (this.interceptors != null && !this.interceptors.isEmpty()) {
                for (Interceptor interceptor : this.interceptors) {
                    builder.addInterceptor(interceptor);
                }
            }
            this.client = builder
                    .readTimeout(buildParam.getReadTimeout(), TimeUnit.SECONDS)
                    .writeTimeout(buildParam.getWriteTimeout(), TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true)
                    .connectTimeout(buildParam.getConnectionTimeout(), TimeUnit.SECONDS).build();
            this.isInit = true;
        }
    }

    @Override
    protected ApiResponse sendSyncRequest(ApiRequest apiRequest) {
        this.checkIsInit();
        Request request = buildRequest(apiRequest);
        Call call = this.client.newCall(request);
        try {
            return getApiResponse(call.execute());
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("TAG","exception:" + e.getMessage());
            return new ApiResponse(500, "Read response occur error", e);
        }
    }

    @Override
    protected void sendAsyncRequest(final ApiRequest apiRequest, final ApiCallback apiCallback) {
        this.checkIsInit();
        Request request = buildRequest(apiRequest);
        Call call = client.newCall(request);
        Callback callback = new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                apiCallback.onFailure(apiRequest, e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                apiCallback.onResponse(apiRequest, getApiResponse(response));
            }
        };
        call.enqueue(callback);
    }

    private Request buildRequest(ApiRequest request) {
        if (TextUtils.isEmpty(request.getHost())) {
            request.setHost(host);
        }
        StringBuilder url = new StringBuilder();
        if (!request.getHost().startsWith("http")) {
            request.setScheme(scheme);
            url.append(request.getScheme().getValue());
        }

        url.append(request.getHost());
        String pathWithPathParameter = combinePathParam(request.getPath(), request.getPathParams());
        if (!TextUtils.isEmpty(pathWithPathParameter)) {
            char slash = 0x2f;
            if (url.length() > 0 && url.charAt(url.length() - 1) != slash) {
                url.append(slash);
            }
            url.append(pathWithPathParameter);
        }

        if (null != request.getQuerys() && request.getQuerys().size() > 0) {
            url.append("?").append(buildParamString(request.getQuerys()));
        }
        request.setUrl(url.toString());
        request.addHeader("content-type", request.getMethod().getRequestContentType());
        request.addHeader("accept", request.getMethod().getAcceptContentType());

        String key;
        List<String> values;
        for (Iterator itKeys = request.getHeaders().keySet().iterator(); itKeys.hasNext(); request.getHeaders().put(key, values)) {
            key = (String) itKeys.next();
            values = request.getHeaders().get(key);
            if (null != values && values.size() > 0) {
                for (int i = 0; i < values.size(); ++i) {
                    byte[] temp = values.get(i).getBytes(StandardCharsets.UTF_8);
                    values.set(i, new String(temp, StandardCharsets.ISO_8859_1));
                }
            }
        }

        RequestBody requestBody = null;
        if (null != request.getFormParams() && request.getFormParams().size() > 0) {
            requestBody = buildBody(request);
        } else if (request.getBody() != null && request.getBody().length > 0) {
            requestBody = RequestBody.create(MediaType.parse(request.getMethod().getRequestContentType()), request.getBody());
        }

        return new Request.Builder()
                .method(request.getMethod().getValue(), requestBody)
                .url(request.getUrl())
                .headers(getHeadersFromMap(request.getHeaders()))
                .build();
    }

    private MultipartBody buildBody(ApiRequest request) {
        Map<String, Object> params = request.getFormParams();
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);

        for (String key : params.keySet()) {
            Object param = params.get(key);
            if (param instanceof File) {
                File file = (File) param;
                builder.addFormDataPart("file", file.getName(), RequestBody.create
                        (MediaType.parse(request.getMethod().getRequestContentType()), file));
            } else {
                try {
                    builder.addFormDataPart(key, URLEncoder.encode((String) param, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return builder.build();
    }

    private String combinePathParam(String path, Map<String, String> pathParams) {
        if (pathParams == null) {
            return path;
        } else {
            String key;
            for (Iterator itKeys = pathParams.keySet().iterator(); itKeys.hasNext(); path = path.replace("{" + key + "}", pathParams.get(key))) {
                key = (String) itKeys.next();
            }
            return path;
        }
    }

    private String buildParamString(Map<String, String> params) {
        StringBuilder result = new StringBuilder();
        if (null != params && params.size() > 0) {
            boolean isFirst = true;

            for (String key : params.keySet()) {
                if (isFirst) {
                    isFirst = false;
                } else {
                    result.append("&");
                }
                try {
                    result.append(key).append("=").append(URLEncoder.encode(params.get(key), "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return result.toString();
    }

    private Headers getHeadersFromMap(Map<String, List<String>> map) {
        List<String> nameAndValues = new ArrayList<>();
        Iterator itEntrySet = map.entrySet().iterator();

        while (itEntrySet.hasNext()) {
            Map.Entry<String, List<String>> entry = (Map.Entry) itEntrySet.next();
            for (String value : entry.getValue()) {
                nameAndValues.add(entry.getKey());
                nameAndValues.add(value);
            }
        }
        return Headers.of((String[]) nameAndValues.toArray(new String[0]));
    }

    private ApiResponse getApiResponse(Response response) throws IOException {
        ApiResponse apiResponse = new ApiResponse(response.code());
        apiResponse.setHeaders(response.headers().toMultimap());
        apiResponse.setBody(response.body().bytes());
        apiResponse.setContentType(response.header("content-type", ""));
        return apiResponse;
    }

    private Map<String, String> getSimpleMapFromRequest(Headers headers) {
        Map<String, List<String>> complexMap = headers.toMultimap();
        Map<String, String> simpleMap = new HashMap<>();
        for (Map.Entry<String, List<String>> stringListEntry : complexMap.entrySet()) {
            simpleMap.put(stringListEntry.getKey(), stringListEntry.getValue().get(0));
        }
        return simpleMap;
    }
}
